/*
 *  Copyright (C) 2010 Zubanov Dmitry
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.jssdb.core.net;

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jssdb.core.event.CustomEvent;
import org.jssdb.core.event.CustomEventListener;

/**
 *
 * @author developer
 */
public class DataChunkCollector {

    private int cleanerTimeout = 5;
    private ArrayList<DataChunk> dataChunkList = new ArrayList();
    private javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();
    private static final Logger logger = Logger.getLogger(DataChunkCollector.class.getName());
    private static final Object sync = new Object();

    private DataChunkCollector() {
        ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
        ses.scheduleWithFixedDelay(cleaner(), 0, cleanerTimeout, TimeUnit.MINUTES);
    }

    public void addCustomEventListener(CustomEventListener listener) {
        listenerList.add(CustomEventListener.class, listener);
    }

    void fireCustomEvent(CustomEvent evt) {
        Object[] listeners = listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == CustomEventListener.class) {
                ((CustomEventListener) listeners[i + 1]).customEventOccurred(evt);
            }
        }
    }

    private Runnable cleaner() {
        Runnable r = new Runnable() {

            public void run() {
                // TODO
            }
        };

        return r;
    }

    public void addChunk(DataChunk dataChunk) {
        synchronized (sync) {
            dataChunkList.add(dataChunk);
            chunkHandler();
        }
    }

    private void chunkHandler() {
        Set<Long> chunkIdSet = new HashSet();
        synchronized (sync) {
            for (DataChunk dataChunk : dataChunkList) {
                chunkIdSet.add(dataChunk.getId());
            }
        }

        for (long id : chunkIdSet) {
            try {
                Object obj = getObjectById(id);
                if (obj != null) {
                    fireCustomEvent(new CustomEvent(obj));
                }
            } catch (Exception ex) {
                logger.log(Level.SEVERE, null, ex);
            }
        }
    }

    private Object getObjectById(long id) throws Exception {
        Object obj = null;

        ArrayList<DataChunk> chunkList = new ArrayList();
        int counter = 0;
        synchronized (sync) {
            for (DataChunk dataChunk : dataChunkList) {
                if (dataChunk.getId() == id) {
                    chunkList.add(dataChunk);
                    counter++;
                }
            }
        }

        if (chunkList.isEmpty()) {
            return obj;
        }

        if (chunkList.get(0).getPartCount() != counter) {
            return obj;
        }

        DataChunk[] dataChunkArray = chunkList.toArray(new DataChunk[chunkList.size()]);
        Arrays.sort(dataChunkArray);
        // restore object
        int len = 0;
        for (DataChunk dataChunk : dataChunkArray) {
            len += dataChunk.getData().length;
        }

        byte[] bData = new byte[len];
        int dstPos = 0;
        for (DataChunk dataChunk : dataChunkArray) {
            System.arraycopy(dataChunk.getData(), 0, bData, dstPos, dataChunk.getData().length);
            dstPos += dataChunk.getData().length;
        }

        ByteArrayInputStream bais = new ByteArrayInputStream(bData);
        ObjectInputStream in = new ObjectInputStream(bais);
        obj = in.readObject();

        // clear
        for (DataChunk dataChunk : dataChunkArray) {
            synchronized (sync) {
                dataChunkList.remove(dataChunk);
            }
        }

        return obj;
    }

    public static DataChunkCollector getInstance() {
        return DataChunkCollectorHolder.INSTANCE;
    }

    private static class DataChunkCollectorHolder {

        private static final DataChunkCollector INSTANCE = new DataChunkCollector();
    }
}
